package com.fly.tcp.bio;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TcpServer implements Runnable
{
    private ServerSocket serverSocket;
    
    private boolean isServerCoreRun = false;
    
    private Map<String, NewClient> allClient = new HashMap<>();
    
    public boolean startServer(String ip, int port)
    {
        try
        {
            serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(ip, port));
            isServerCoreRun = true;
        }
        catch (IOException e)
        {
            log.error(e.getMessage());
            isServerCoreRun = false;
        }
        return isServerCoreRun;
    }
    
    /**
     * 关闭服务
     *
     * #1 断开与所有客户端的连接，并将客户端容器中的所有已连接的客户端清空。 #2 关闭服务器套接字
     */
    public void closeServer()
    {
        try
        {
            isServerCoreRun = false;
            for (Map.Entry<String, NewClient> all : this.allClient.entrySet())
            {
                all.getValue().isNewClientRun = false;
                all.getValue().socket.close();
            }
            allClient.clear();
            serverSocket.close();
        }
        catch (IOException e)
        {
            log.error(e.getMessage());
        }
    }
    
    /**
     * 向客户端发送报文
     */
    public void sendMsg(String clientName, String msg)
    {
        if (allClient.containsKey(clientName))
        {
            allClient.get(clientName).sendMsg(msg);
        }
    }
    
    @Override
    public void run()
    {
        try
        {
            log.info("TcpServer will start");
            while (isServerCoreRun)
            {
                // 阻塞式等待客户端连接
                Socket socket = serverSocket.accept();
                String clientName = new DataInputStream(socket.getInputStream()).readUTF();
                String clientIP = socket.getInetAddress().getHostAddress();
                int clientPort = socket.getPort();
                String clientConnectDateTime = DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss");
                NewClient newClient = new NewClient(socket, clientName, clientIP, clientPort, clientConnectDateTime);
                allClient.put(clientName, newClient);
                log.info("**** add new client ===> {}", allClient.keySet());
                new Thread(newClient).start();
            }
        }
        catch (IOException e)
        {
            log.error(e.getMessage());
        }
    }
    
    class NewClient implements Runnable
    {
        // 客户端套接字
        private Socket socket;
        
        // 数据输入流
        private DataInputStream dataInputStream;
        
        // 数据输出流
        private DataOutputStream dataOutputStream;
        
        // 客户端运行(收、发报文)状态
        private boolean isNewClientRun = true;
        
        // 客户端的名称
        private String clientName;
        
        // 客户端的IP地址
        private String clientIP;
        
        public NewClient()
        {
        }
        
        // 构造方法初始化成员属性
        public NewClient(Socket socket, String clientName, String clientIP, int clientPort, String clientConnectDateTime)
        {
            this.socket = socket;
            this.clientName = clientName;
            this.clientIP = clientIP;
            try
            {
                // 创建客户端数据输入、输出流
                dataInputStream = new DataInputStream(socket.getInputStream());
                dataOutputStream = new DataOutputStream(socket.getOutputStream());
            }
            catch (IOException e)
            {
                log.error(e.getMessage());
                closeCurrentClient();
            }
        }
        
        @Override
        public void run()
        {
            try
            {
                // 客户端在运行才能收发报文
                while (this.isNewClientRun)
                {
                    // 获取到客户端发送的报文
                    String msg = dataInputStream.readUTF();
                    if (StringUtils.isNotBlank(msg))
                    {
                        log.info("clientName: {}, clientIP: {}, send msg ===> {}", clientName, clientIP, msg);
                    }
                    
                    // 向客户端传送数据
                    int index = 0;
                    for (String key : allClient.keySet())
                    {
                        index++;
                        if (StringUtils.equals(key, clientName))
                        {
                            allClient.get(key).sendMsg("from server: " + msg + StringUtils.repeat("-----", index));
                        }
                    }
                }
            }
            catch (IOException e)
            {
                log.error(e.getMessage());
                closeCurrentClient();
            }
        }
        
        /**
         * 断开当前客户端的连接释放资源
         */
        public void closeCurrentClient()
        {
            try
            {
                // 结束客户端的运行状态
                isNewClientRun = false;
                // 断开数据输出出流
                if (dataOutputStream != null)
                {
                    dataOutputStream.close();
                }
                // 断开数据输入出流
                if (dataInputStream != null)
                {
                    dataInputStream.close();
                }
                // 断开客户端套解析
                if (socket != null)
                {
                    socket.close();
                }
                // 将该客户端从客户端容器中删除
                allClient.remove(clientName);
                log.info("**** remove client ===> {}", allClient.keySet());
            }
            catch (IOException e)
            {
                log.error(e.getMessage());
            }
        }
        
        /**
         * 发送报文
         */
        public void sendMsg(String msg)
        {
            try
            {
                // 发送报文
                dataOutputStream.writeUTF(msg);
                // 清空报文缓存
                dataOutputStream.flush();
            }
            catch (IOException e)
            {
                log.error(e.getMessage());
                closeCurrentClient();
            }
        }
    }
}
